﻿using Games.Common;
using Games.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Games.Klotski
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    partial class GamePage : ContentPage
    {
        IDataBase database = DependencyService.Get<IDataBase>();
        Game game;
        GameInfo gameInfo;
        int margin;
        int cellSize;
        double layoutWidth;
        Data map = new Data();
        KlotskiLevel boxLevel;
        ScrollView view;
        List<Button> buttons;
        internal GamePage(GameInfo gamePage)
        {
            InitializeComponent();
            BindingContext = gameInfo = gamePage;
            game = new Game();
            LoadAsync().Wait();
            database.CreateTable<KlotskiRecord>();
        }
        async Task LoadAsync()
        {
            var gameArgs = await database.GetFirstOrDefaultAsync<GameArgs>(x => x.GameID == gameInfo.ID).ConfigureAwait(false);
            if(gameArgs != null)
            {
                boxLevel = BaseArgs.Convert<KlotskiLevel>(gameArgs);
            }
            else
            {
                boxLevel = new KlotskiLevel()
                {
                    GameID = gameInfo.ID,
                    //Version = gameInfo.Version,
                    Level = 0,
                    MaxLevel = 0
                };
            }
            if(Utils.NoMaxLimit)
            {
                boxLevel.MaxLevel = map.Maps.Length;
            }
        }
        async Task SaveArgsAsync()
        {
            await database.SaveAsync(boxLevel.ToGameArgs());
        }
        void OnLayoutSizeChanged(object sender, EventArgs args)
        {
            Layout layout = sender as Layout;
            var bw = layout.Width;
            var bh = layout.Height;
            layoutWidth = Math.Min(bw, bh);
            margin = (int)layoutWidth / 20;
            layoutWidth = margin * 20;
            cellSize = margin * 3;

            var mw = (bw - layoutWidth) / 2;
            var mh = (bh - layoutWidth) / 2;
            var rect = new Rectangle(mw, mh, layoutWidth, layoutWidth);
            AbsoluteLayout.SetLayoutBounds(imgArea, rect);
            AbsoluteLayout.SetLayoutBounds(absoluteLayout, rect);

            game.Init(map.Maps[boxLevel.Level]);
            InitLayout();
        }

        void InitLayout()
        {
            gameInfo.Level = boxLevel.Level + 1;
            gameInfo.StepCount = 0;
            absoluteLayout.Children.Clear();
            var pan = new PanGestureRecognizer();
            pan.PanUpdated += this.Pan_PanUpdated;
            foreach(var item in game.Pieces)
            {
                GameCell lifeCell = new GameCell(item);
                lifeCell.GestureRecognizers.Add(pan);
                absoluteLayout.Children.Add(lifeCell);
                Rectangle rect = new Rectangle(item.X * cellSize + margin - 1, item.Y * cellSize + margin - 1, cellSize * item.W, cellSize * item.H);
                AbsoluteLayout.SetLayoutBounds(lifeCell, rect);
                lifeCell.Show();
            }
            TimeCounter.Start();
        }

        private double cx, cy;
        private double panx, pany;
        private int minPos, maxPos;
        private GameCell activeCell = null;
        async void Pan_PanUpdated(object sender, PanUpdatedEventArgs e)
        {
            if(game.IsFinished)
            {
                return;
            }
            var cell = (GameCell)sender;
            if(e.StatusType == GestureStatus.Started)
            {
                if(activeCell != null)
                {
                    return;
                }
                activeCell = cell;
                var (min, max) = game.GetPieceRange(activeCell.Piece);
                minPos = min * cellSize + margin - 1;
                maxPos = max * cellSize + margin - 1;
                var fromBound = AbsoluteLayout.GetLayoutBounds(activeCell);
                cx = fromBound.X;
                cy = fromBound.Y;
                return;
            }
            else if(e.StatusType == GestureStatus.Completed)
            {
                if(activeCell == null || activeCell != cell)
                {
                    return;
                }
                var piece = activeCell.Piece;
                if(piece.IsHorizontal)
                {
                    var dx = ((int)panx + (panx > 0 ? cellSize : -cellSize) / 2) / cellSize;
                    if(dx != 0)
                    {
                        game.Move(piece, dx, 0);
                        gameInfo.StepCount++;
                    }
                }
                else
                {
                    var dy = ((int)pany + (pany > 0 ? cellSize : -cellSize) / 2) / cellSize;
                    if(dy != 0)
                    {
                        game.Move(piece, 0, dy);
                        gameInfo.StepCount++;
                    }
                }
                var tox = piece.X * cellSize + margin - 1;
                var toy = piece.Y * cellSize + margin - 1;
                if(game.IsFinished)
                {
                    await Task.WhenAll(Move(piece, tox + cellSize * 2, toy, 250), cell.FadeTo(0, 250));
                    await ShowFinish();
                }
                else
                {
                    await Move(piece, tox, toy);
                }
                activeCell = null;
            }
            else if(e.StatusType == GestureStatus.Running)
            {
                if(activeCell == null || activeCell != cell)
                {
                    return;
                }
                var piece = activeCell.Piece;
                var fromBound = AbsoluteLayout.GetLayoutBounds(cell);
                panx = e.TotalX + fromBound.X - cx;
                pany = e.TotalY + fromBound.Y - cy;
                if(piece.IsHorizontal)
                {
                    var tox = Math.Max(minPos, Math.Min(maxPos, (int)(cx + panx)));
                    await Move(piece, tox, (int)cy);
                }
                else
                {
                    var toy = Math.Max(minPos, Math.Min(maxPos, (int)(cy + pany)));
                    await Move(piece, (int)cx, toy);
                }
            }
        }

        Task Move(Piece piece, int tox, int toy, uint length = 125)
        {
            var cell = (GameCell)absoluteLayout.Children[piece.Id];
            var fromBound = AbsoluteLayout.GetLayoutBounds(cell);
            var fromx = fromBound.X;
            var fromy = fromBound.Y;
            var taskCompletionSource = new TaskCompletionSource<bool>();
            cell.Animate<Rectangle>(
                "Push",
                t => new Rectangle(fromx + t * (tox - fromx), fromy + t * (toy - fromy), fromBound.Width, fromBound.Height),
                r => AbsoluteLayout.SetLayoutBounds(cell, r),
                16,
                length,
                Easing.Linear,
                (r, b) => taskCompletionSource.SetResult(b)
            );
            return taskCompletionSource.Task;
        }

        async Task ShowFinish()
        {
            var record = await SaveRecordAsync();
            if(boxLevel.Level >= map.Maps.Length - 1)
            {
                await DisplayAlert(record.Level, record.Message, "OK");
                return;
            }
            var flag = await DisplayAlert(record.Level, $"{record.Message}\r\n\r\n是否进入下一关？", "YES", "NO");
            if(boxLevel.MaxLevel == boxLevel.Level && boxLevel.MaxLevel < map.Maps.Length - 1)
            {
                boxLevel.MaxLevel++;
                if(this.buttons != null)
                {
                    this.buttons[boxLevel.MaxLevel].IsEnabled = true;
                }
            }
            if(flag)
            {
                boxLevel.Level++;
                await SaveArgsAsync();
                game.Init(map.Maps[boxLevel.Level]);
                InitLayout();
            }
            else
            {
                await SaveArgsAsync();
            }
        }
        async Task<Record> SaveRecordAsync()
        {
            var dur = TimeCounter.Stop();
            var cRecord = new KlotskiRecord()
            {
                MinStep = game.MinStep,
                Step = gameInfo.StepCount,
                GameID = gameInfo.ID,
                CreateTime = DateTime.Now,
                Level = boxLevel.Level + 1,
                Duration = dur
            };
            await database.SaveRecordByLevel<KlotskiRecord>(cRecord,
                o => o.Level == cRecord.Level,
                o => o.Step > cRecord.Step || (o.Step == cRecord.Step && o.Duration > cRecord.Duration)
            );
            return cRecord.Convert();
        }
        async void LevelClicked(object sender, EventArgs args)
        {
            if(!mask.Contains("levels"))
            {
                (view, buttons) = ControlUtil.GetLevelGrid(map.Maps.Length, boxLevel.MaxLevel, 5, LevelSelect);
                mask.Add("levels", "选择关卡", view);
            }
            mask.ShowWithCancel("levels");
            await view.ScrollToAsync(this.buttons[boxLevel.Level], ScrollToPosition.Center, false);
        }
        async void LevelSelect(int level)
        {
            mask.Hide();
            if(boxLevel.Level != level - 1)
            {
                boxLevel.Level = level - 1;
                await SaveArgsAsync();
            }
            game.Init(map.Maps[boxLevel.Level]);
            InitLayout();
        }
        void RestartClicked(object sender, EventArgs args)
        {
            game.Init(map.Maps[boxLevel.Level]);
            InitLayout();
        }

        void OnAboutButtonClicked(object sender, EventArgs args)
        {
            if(!mask.Contains("about"))
            {
                var aboutView = ControlUtil.GetAbout(gameInfo);
                mask.Add("about", gameInfo.Name, aboutView);
            }
            mask.ShowWithCancel("about", "确定");
        }
        void OnRecordClicked(object sender, EventArgs args)
        {
            var sorter = new Sorter<KlotskiRecord>(q => q.OrderByDescending(x => x.Level));
            Navigation.PushAsync(new RecordPage(gameInfo.Name, new RecordViewModel<KlotskiRecord>(sorter)));
        }

        private void TapToBack(object sender, EventArgs e)
        {
            Navigation.PopAsync();
        }
    }
}